/**************************************************************
 * metainfo.c - metainfo of media file
 * Copyright (c) 2009  Qin Xiaoxue  <crs.chin@gmail.com>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 * 
 **************************************************************/


/* NOTE: current ONLY mp3 format is supported */

#include <string.h>

#include "common.h"
#include "metainfo.h"
#include "encoding_convert.h"
#include "metainfo_mp3.h"
#include "metainfo_ape.h"

struct __metainfo{
  gint         fmt;
  const gchar *path;
  gboolean     modified;
  gint         flag;
  const gchar *encoding;
  const gchar *orignal_encoding;
  struct meta  meta;
};


#define DECLARE_FORMAT(name,probe,read,write,read_property,set_property,release) \
  {#name, probe, read, write, read_property, set_property, release },

static struct media_format format_list[] = {
#include "metainfo_formats.h"
  DECLARE_FORMAT(NULL, NULL, NULL, NULL, NULL, NULL, NULL)
#undef DECLARE_FORMAT
};


static gboolean sync_encoding(metainfo_t *mi)
{
  gchar *tmp;

  if(mi->orignal_encoding)  {
	if(! strcasecmp(mi->encoding, mi->orignal_encoding))  {
	  mi->meta.sync = TRUE;
	  return TRUE;
	}

#ifdef FORCE_ICONVERT
#define __exchange_encoding(from,to)			\
	do{											\
	  g_free((void *)(from));					\
	  from = to;								\
	}while(0)
#else
#define __exchange_encoding(from,to)			\
	do{											\
	  if(! (to))  break;						\
	  g_free((void *)(from));					\
	  from = to;								\
	}while(0)
#endif

#define __convert_meta_encoding(attrib)							\
	if(mi->meta.attrib)  {										\
	  tmp = g_convert(mi->meta.attrib, -1, mi->encoding,		\
					  mi->orignal_encoding, NULL, NULL, NULL);	\
	  __exchange_encoding(mi->meta.attrib, tmp);				\
	}

	__convert_meta_encoding(artist);
	__convert_meta_encoding(title);
	__convert_meta_encoding(album);
	__convert_meta_encoding(year);
	__convert_meta_encoding(track);
	__convert_meta_encoding(genre);
	__convert_meta_encoding(comment);

#undef __convert_meta_encoding
  }else  {

#define __convert_meta_encoding(attrib)								\
	if(mi->meta.attrib)  {											\
	  tmp = zh_convert(mi->meta.attrib, strlen(mi->meta.attrib),	\
					   NULL,  mi->encoding);						\
	  __exchange_encoding(mi->meta.attrib, tmp);					\
	}

	__convert_meta_encoding(artist);
	__convert_meta_encoding(title);
	__convert_meta_encoding(album);
	__convert_meta_encoding(year);
	__convert_meta_encoding(track);
	__convert_meta_encoding(genre);
	__convert_meta_encoding(comment);

#undef __convert_meta_encoding
  }

  mi->meta.sync = TRUE;
  return TRUE;
}



metainfo_t *metainfo_get_metainfo(const gchar *url, const gchar *encoding, gint flag)
{
  const gchar *path, *fmt;
  struct media_format *mformat = format_list;
  FILE *fp;
  metainfo_t *mi = NULL;

  if(! url)
	return NULL;
  path = url;
  while(g_ascii_isspace(*path)) path++;
  if(! strncasecmp(path, "file://", 7))
	path += 7;
  fmt = strrchr(path, '.');
  if(fmt)  fmt++;
  fp = fopen(path, "r");
  if(! fp)  {
	DBGMSG("failed opening file: %s\n", path);
	return NULL;
  }
  
  if(fmt &&  *fmt)
	while(mformat->fmt)  {
	  if(! strcasecmp(fmt, mformat->fmt))
		break;
	  mformat++;
	}

  if(! mformat->fmt || ! fmt || ! *fmt)  {
	mformat = format_list;
	while(mformat->fmt)  {
	  if(mformat->probe)
		if((mformat->probe)(fp))
		  break;
	  mformat++;
	}
  }

  if(! mformat->fmt)   {
	fclose(fp);
	DBGMSG("MI: file type not supprted.\n");
	return NULL;
  }

  mi = g_new0(metainfo_t, 1);
  if(! (mformat->read)(fp, &mi->meta))  {
	g_free(mi);
	fclose(fp);
	DBGMSG("MI: Failed to get metainfo from file\n");
	return NULL;
  }

  mi->orignal_encoding = g_strdup(mi->meta.internal_encoding);

  mi->path = strdup(path);
  mi->fmt = mformat - format_list;
  if(encoding && *encoding)
	mi->encoding = strdup(encoding);
  else
	mi->encoding = strdup("utf-8");

  sync_encoding(mi);

  mi->flag = flag;
  mi->modified = FALSE;

  return mi;
}

#define __set_err(mi,str)						\
  do{											\
	mi->meta.iserr = TRUE;						\
	mi->meta.errstr = str;						\
  }while(0)


#define __metainfo_get_attribute(attrib)				\
  const gchar *metainfo_get_##attrib(metainfo_t *mi)	\
  {														\
	if(! mi) return NULL;								\
	if(! mi->meta.sync)									\
	  sync_encoding(mi);								\
	return mi->meta.attrib;								\
  }

__metainfo_get_attribute(artist);
__metainfo_get_attribute(title);
__metainfo_get_attribute(album);
__metainfo_get_attribute(year);
__metainfo_get_attribute(track);
__metainfo_get_attribute(genre);
__metainfo_get_attribute(comment);

#undef __metainfo_get_attribute


#define __metainfo_set_attribute(attrib)									\
  gboolean metainfo_set_##attrib(metainfo_t *mi, const gchar *attrib)	\
  {																\
	if(! mi)  return FALSE;										\
	if(! attrib)  {												\
	  __set_err(mi,"invalid arguments");						\
	  return FALSE;												\
	}															\
	if(mi->meta.attrib)											\
	  g_free((void *)mi->meta.attrib);							\
	mi->meta.attrib = g_strdup(attrib);							\
	mi->modified = TRUE;										\
	return FALSE;												\
  }

__metainfo_set_attribute(artist);
__metainfo_set_attribute(title);
__metainfo_set_attribute(album);
__metainfo_set_attribute(year);
__metainfo_set_attribute(track);
__metainfo_set_attribute(genre);
__metainfo_set_attribute(comment);

#undef __metainfo_set_attribute

gchar *metainfo_get_property(metainfo_t *mi, const gchar *property)
{
  gchar *value, *ret;

  if(! mi)
	return NULL;
  if(! property)  {
	__set_err(mi, "invalid arguments");
	return NULL;
  }

  if(! format_list[mi->fmt].get_property)  {
	__set_err(mi, "not supported");
	return NULL;
  }
  
  value = (format_list[mi->fmt].get_property)(&mi->meta, property);
  if(! value)
	return NULL;

  if(mi->meta.internal_encoding)  {
	if(strcasecmp(mi->encoding, mi->meta.internal_encoding))  {
	  ret = g_convert(value, -1, mi->encoding, mi->meta.internal_encoding, NULL, NULL, NULL);
#ifdef FORCE_ICONVERT
	  g_free(value);
	  if(! ret)
		__set_err(mi, "encoding convert failed");
	  return ret;
#else
	  if(! ret)
		return value;
	  g_free(value);
	  return ret;
#endif
	}

	return value;
  }else  {
	ret = zh_convert(value, strlen(value), NULL, mi->encoding);
#ifdef FORCE_ICONVERT
	g_free(value);
	if(! ret)
	  __set_err(mi, "encoding convert failed");
	return ret;
#else
	if(! ret)
	  return value;
	g_free(value);
	return ret;
#endif
  }
}


const gchar *metainfo_get_format(metainfo_t *mi)
{
  if(! mi)	return NULL;
  return format_list[mi->fmt].fmt;
}


const gchar *metainfo_get_path(metainfo_t *mi)
{
  if(! mi)  return NULL;
  return mi->path;
}

const gchar *metainfo_get_encoding(metainfo_t *mi)
{
  if(! mi)  return NULL;
  return mi->encoding;
}


gboolean metainfo_set_property(metainfo_t *mi, const gchar *property, const gchar *value)
{
  gboolean ret;
  const gchar *_value;

  if(! mi) return FALSE;
  if(! property)  __set_err(mi, "invalid arguments");
  if(! format_list[mi->fmt].set_property)  {
	__set_err(mi, "not supported");
	return FALSE;
  }

  if(mi->meta.internal_encoding)  {
	if(strcasecmp(mi->encoding, mi->meta.internal_encoding))  {
	  _value = g_convert(value, -1, mi->meta.internal_encoding, mi->encoding, NULL, NULL, NULL);
#ifdef FORECE_ICONVERT
	  if(! _value)  {
		__set_err(mi, "encoding convert failed");
		return FALSE;
	  }
#else
	  if(! _value)
		_value = value;
#endif
	}else
	  _value = value;
  }else
	_value = value;

  ret = (format_list[mi->fmt].set_property)(&mi->meta, property, _value);
  mi->modified = ret;

  return ret;
}


gboolean metainfo_set_encoding(metainfo_t *mi, const gchar *encoding)
{
  if(! mi)  return FALSE;
  if(! encoding || ! *encoding)  {
	__set_err(mi, "invalid arguments");
	return FALSE;
  }

  if(! strcasecmp(encoding, mi->encoding))
	return TRUE;

  g_free((void *)mi->orignal_encoding);
  mi->orignal_encoding = mi->encoding;
  mi->encoding = strdup(encoding);
  mi->meta.sync = FALSE;

  return sync_encoding(mi);
}

void   metainfo_clearerr_metainfo(metainfo_t *mi)
{
  if(! mi)  return;
  mi->meta.iserr = FALSE;
}


const gchar *metainfo_strerr_metainfo(metainfo_t *mi)
{
  if(! mi || ! mi->meta.iserr) return NULL;
  return mi->meta.errstr;
}

gboolean metainfo_write_metainfo(metainfo_t *mi, const gchar *path)
{
  const gchar *realpath, *tmp;
  FILE *fp;
  gboolean  reverse_encoding = FALSE,ret;

  if(! mi)  return FALSE;
  if(! format_list[mi->fmt].write)
	__set_err(mi, "not supported");

  if(path)
	realpath = path;
  else
	realpath = mi->path;

  fp = fopen(realpath, "w");
  if(! fp)  {
	__set_err(mi, "error opening file for writting");
	return FALSE;
  }

  if(mi->flag | METAINFO_KEEPENCODING  && mi->meta.internal_encoding)
	if(strcasecmp(mi->encoding, mi->meta.internal_encoding))  {
	tmp = mi->encoding;
	mi->encoding = mi->meta.internal_encoding;
	mi->orignal_encoding = tmp;
	mi->meta.sync = FALSE;
	sync_encoding(mi);
	reverse_encoding = TRUE;
  }

  ret = (format_list[mi->fmt].write)(fp, &mi->meta);
  mi->modified = ! ret;

  if(reverse_encoding)  {
	mi->orignal_encoding = mi->encoding;
	mi->encoding = tmp;
	mi->meta.sync = FALSE;
	sync_encoding(mi);
  }

  return ret;
}


void metainfo_put_metainfo(metainfo_t *mi)
{
  if(! mi)   return;

  if(mi->flag | METAINFO_WRITEBACK && mi->modified)
	metainfo_write_metainfo(mi, NULL);
  if(format_list[mi->fmt].release)
	(format_list[mi->fmt].release)(&mi->meta);

  g_free((void *)mi->path);
  g_free((void *)mi->encoding);

  g_free((void *)mi->meta.artist);
  g_free((void *)mi->meta.title);
  g_free((void *)mi->meta.album);
  g_free((void *)mi->meta.year);
  g_free((void *)mi->meta.track);
  g_free((void *)mi->meta.genre);
  g_free((void *)mi->meta.comment);

  g_free(mi);
}

